/* Copyright (c) 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//
//  GDataFeedTest.m
//

#import "GData.h"

#import "GDataFeedTest.h"
#import "GDataElementsTest.h"

#import "GDataEntryCalendarEvent.h"
#import "GDataEntryYouTubeVideo.h"
#import "GDataEntryHealthProfile.h"
#import "GDataMapConstants.h"


@implementation GDataFeedTest

- (void)runTests:(TestKeyPathValues *)tests {
  
  // step through each feed test
  for (int testIndex = 0;
       tests[testIndex].str1 != nil;
       testIndex++) {
    
    // get the class of this feed and the path to the test file of xml data 
    //
    // The class name may specify a service version, like GDataFeedCalendar/2.0,
    // and may have a "-ignoreUnknown" suffix indicating unknown elements should be
    // ignored, like GDataFeedCalendar-ignore/2.0

    NSString *className = tests[testIndex].str1;
    NSString *serviceVersion = nil;
    BOOL shouldIgnoreUnknowns = NO;
    
    NSArray *components = [className componentsSeparatedByString:@"/"];
    if ([components count] == 2) {
      className = [components objectAtIndex:0];
      serviceVersion = [components objectAtIndex:1];
    }

    if ([className hasSuffix:@"-ignoreUnknown"]) {
      shouldIgnoreUnknowns = YES;

      NSUInteger nameLen = [className length] - [@"-ignoreUnknown" length];
      className = [className substringToIndex:nameLen];
    }
    
#ifdef GDATA_TARGET_NAMESPACE
    className = [NSString stringWithFormat:@"%s_%@",
                 GDATA_TARGET_NAMESPACE_STRING, className];
#endif

    Class gdataClass = NSClassFromString(className);
    STAssertNotNil(gdataClass, @"Cannot make class for class name: %@", className);
    
    NSString *feedPath = tests[testIndex].str2;
    NSData *data = [NSData dataWithContentsOfFile:feedPath];
    STAssertNotNil(data, @"Cannot read feed from %@", feedPath);
    
    // create the feed object
    GDataFeedBase *feed1 = nil;
    if ([gdataClass instancesRespondToSelector:@selector(initWithData:serviceVersion:)]) {
      feed1 = [[[gdataClass alloc] initWithData:data
                                 serviceVersion:serviceVersion
                           shouldIgnoreUnknowns:shouldIgnoreUnknowns] autorelease];
    } else {
      // this "feed" isn't a proper feed, and might be an entry
      NSError *error = nil;
      NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithData:data
                                                        options:0
                                                          error:&error] autorelease];
      STAssertNotNil(doc, @"Cannot allocate XML document for %@, error %@",
                     feedPath, error);

      NSXMLElement* root = [doc rootElement];
      feed1 = [[[gdataClass alloc] initWithXMLElement:root 
                                               parent:nil
                                       serviceVersion:serviceVersion
                                           surrogates:nil
                                 shouldIgnoreUnknowns:shouldIgnoreUnknowns] autorelease];
    }
    
    // copy the feed object 
    GDataObject *feed1copy = [[feed1 copy] autorelease];
    
    STAssertTrue([feed1 isEqual:feed1copy], @"Failed copy feed (%@) from %@ to %@",
                 feedPath, feed1, feed1copy);

    // make a new feed object we'll test against from XML generated by the copy
    NSXMLElement *outputXML = [feed1copy XMLElement];
        
    GDataFeedBase *feed2 = [[[gdataClass alloc] initWithXMLElement:outputXML
                                                            parent:nil
                                                    serviceVersion:serviceVersion
                                                        surrogates:nil
                                              shouldIgnoreUnknowns:shouldIgnoreUnknowns] autorelease];

    STAssertTrue([feed2 isEqual:feed1copy], @"Failed for %@ using XML \n  %@\n\nto convert\n  %@ \nto\n  %@",  
                 feedPath, outputXML, feed1copy, feed2);

    // generate a description; this can fire an exception for an invalid keyPath
    // in the description record list
    STAssertNotNil([feed2 description], @"Could not generate description for %@",
                   feedPath);
    
    // step through all the key-value path tests
    while (1) {
      
      ++testIndex;
      
      NSString *keyPath = tests[testIndex].str1;
      NSString *expectedValue = tests[testIndex].str2;
      
      if (keyPath == nil || [keyPath length] == 0) break;
      
#if GDATA_USES_LIBXML
      // skip the XMLStrings until we can normalize whitespace and closing
      // brackets and other minor differences
      if ([keyPath hasSuffix:@".XMLString"]) continue;
#endif

      NSString *result = [GDataElementsTest valueInObject:feed2
                                forKeyPathIncludingArrays:keyPath];
      
      // if the result wasn't a string but responds to stringValue, then
      // invoke that to get a string
      if ([expectedValue isKindOfClass:[NSString class]]
          && ![result isKindOfClass:[NSString class]]
          && [result respondsToSelector:@selector(stringValue)]) {
        
        result = [(id)result stringValue];     
      }
      
      // we'll test for equality unless the expected result begins "hasPrefix:"
      // or "contains:"
      if ([expectedValue hasPrefix:@"hasPrefix:"]) {
        NSString *prefix = [expectedValue substringFromIndex:[@"hasPrefix:" length]];
        STAssertTrue([result hasPrefix:prefix], @"failed object %@ \n testing key path '%@' for prefix:\n %@ \n!= prefix:\n %@", 
                     feed2, keyPath, result, prefix);  
        
      } else if ([expectedValue hasPrefix:@"contains:"]) {
        
        NSString *substring = [expectedValue substringFromIndex:[@"contains:" length]];
        NSRange range = [result rangeOfString:substring];
        STAssertTrue(result != nil && range.location != NSNotFound, 
                     @"failed object %@ \n testing key path '%@' for substring:\n %@ \n!= contains:\n %@", 
                     feed2, keyPath, result, substring);      
      } else {

#ifdef GDATA_TARGET_NAMESPACE
        // tests for class name need the prefix added
        if ([keyPath hasSuffix:@"className"]
            && [expectedValue hasPrefix:@"GData"]) {

          expectedValue = [NSString stringWithFormat:@"%s_%@",
                           GDATA_TARGET_NAMESPACE_STRING, expectedValue];
        }
#endif

        STAssertTrue(AreEqualOrBothNil(result, expectedValue), @"failed object %@ \n testing key path '%@'\n %@ \n!= \n %@", 
                     feed2, keyPath, result, expectedValue);      
      }
    }
  }  
}

- (void)testBooksFeed {
  
  //
  // Volumes Feed
  //
  TestKeyPathValues tests[] =
  { 
    { @"GDataFeedVolume", @"Tests/FeedBooksVolumesTest1.xml" },
    
    // GDataFeedVolume paths 
    { @"identifier", @"http://www.google.com/books/feeds/users/1728172424007912469/volumes" },
    { @"authors.0.name", @"1728172424007912469" },
    
    // GDataEntryVolume paths
    { @"entries.0.creators.0", @"Jim Davis" },
    { @"entries.0.dates.0", @"2006-01-31" },
    { @"entries.0.volumeDescriptions.0", @"contains:Pig Out" },
    { @"entries.0.embeddability", kGDataBooksNotEmbeddable },
    { @"entries.0.openAccess", kGDataBooksEnabled },
    { @"entries.0.formats.0", @"93 pages" },
    { @"entries.0.volumeIdentifiers.0", @"_PRJAAAACAAJ" },
    { @"entries.0.volumeIdentifiers.1", @"ISBN:0345464664" },
    { @"entries.0.publishers.0", @"Ballantine Books" },
    { @"entries.0.subjects.0", @"Humor" },
    { @"entries.0.volumeTitles.0", @"Garfield Pigs Out" },
    { @"entries.0.viewability", kGDataBooksViewNoPages },
    { @"entries.0.thumbnailLink.href", @"hasPrefix:http://bks2.books.google.com/books?id=_PRJAAAACAAJ" },
    { @"entries.0.previewLink.href", @"contains:id=_PRJAAAACAAJ&ie=ISO-8859-1" },
    { @"entries.0.infoLink.href", @"contains:id=_PRJAAAACAAJ&ie=ISO-8859-1" },
    { @"entries.0.rating.value", @"3" },
    { @"entries.0.rating.average", @"2" },
    { @"entries.0.review", @"Ageless? No way." },
    { @"entries.0.contentVersion", @"1.2beta5" },
    { @"", @"" },

    { nil, nil }
  };
  
  [self runTests:tests];
}

- (void)testCalendarFeed {
  
  TestKeyPathValues tests[] =
  { 
    //
    // Calendar Feed
    //
    { @"GDataFeedCalendar/2.0", @"Tests/FeedCalendarTest1.xml" },
      
    // GDataFeedCalendar paths 
    { @"title", @"Fred Flintstone's Calendar List" },
    { @"links.1.rel", kGDataLinkRelPost },
    { @"links.2.rel", @"self" },
    { @"authors.0.name", @"Fred Flintstone" },
    { @"authors.0.email", @"fred@gmail.com" },
    { @"generator.URI", @"http://www.google.com/calendar" },
    { @"generator.name", @"Google Calendar" },
    { @"generator.version", @"1.0" },
    { @"startIndex", @"1" },
    { @"itemsPerPage", @"3" },
    { @"ETag", @"Nofzeigeritznum" },
    { @"fieldSelection", @"@gd:*,link" },
      
    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },
      
    // GDataEntryCalendar paths
    { @"entries.0.identifier", @"http://www.google.com/calendar/feeds/test%40domain.net/test%40domain.net" },
    { @"entries.0.publishedDate.RFC3339String", @"2006-11-14T00:03:38Z" },
    { @"entries.0.updatedDate.RFC3339String", @"2006-11-09T00:16:10Z" },
    { @"entries.0.editedDate.RFC3339String", @"2006-11-09T00:16:15Z" },
    { @"entries.0.title", @"Fred Flintstone" },
    { @"entries.0.links.0.rel", @"alternate" },
    { @"entries.0.links.1.href", @"http://www.google.com/calendar/feeds/test%40domain.net/test%40domain.net" },
    { @"entries.0.authors.0.name", @"Fred Flintstone" },
    { @"entries.0.authors.0.email", @"fred@gmail.com" },
    { @"entries.0.isHidden", @"0" },
    { @"entries.0.timeZoneName", @"America/Los_Angeles" },
    { @"entries.0.timesCleaned", @"31" },
    { @"entries.0.color", @"#B1365F" },
    { @"entries.0.accessLevel", kGDataCalendarAccessOwner},
    { @"entries.0.overrideName", @"over-ride-name" },
    { @"entries.0.ETag", @"W/C04EQXc6fCp7ImA9WxZbGUU." },
    { @"entries.0.fieldSelection", @"@gd:*,title,gd:when" },
    { @"entries.1.locations.0", @"Joes Pub" },
    { @"entries.1.fieldSelection", nil },
    { @"entries.2.isSelected", @"0" },
    { @"entries.2.isHidden", @"1" },
    
    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed
    
    
    //
    // CalendarEvent Feed
    //
    { @"GDataFeedCalendarEvent", @"Tests/FeedCalendarEventTest1.xml" },
    
    // GDataFeedCalendarEvent paths
    { @"title", @"Fred Flintstone" },
    { @"subtitle", @"Fred Flintstone" },
    { @"links.0.rel", kGDataLinkRelFeed },
    { @"links.2.rel", @"self" },
    { @"authors.0.name", @"Fred Flintstone" },
    { @"authors.0.email", @"fred@gmail.com" },
    { @"identifier", @"http://www.google.com/calendar/feeds/test%40gmail.com/private/full" },
    { @"namespaces.gCal", kGDataNamespaceGCal },
    
    { @"generator.URI", @"http://www.google.com/calendar" },
    { @"generator.name", @"Google Calendar" },
    { @"generator.version", @"1.0" },
    { @"startIndex", @"1" },
    { @"itemsPerPage", @"100000" },
    { @"timeZoneName", @"America/Los_Angeles" },
    { @"timesCleaned", @"7" },
    
    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },
    
    // GDataEntryCalendarEvent paths
    { @"entries.0.identifier", @"contains:i12d4avieju0vogcga72aj3908" },
    
    { @"entries.0.publishedDate.RFC3339String", @"2006-10-27T22:48:14Z" },
    { @"entries.0.updatedDate.RFC3339String", @"2006-11-03T21:17:40Z" },
    { @"entries.0.title", @"3 days" },
    { @"entries.0.content", @"The description field" },

    { @"entries.0.links.0.title", @"alternate" },
    { @"entries.0.links.1.rel", @"self" },
    { @"entries.0.authors.0.name", @"Fred Flintstone" },
    { @"entries.0.authors.0.email", @"fred@gmail.com" },
    { @"entries.0.visibility", kGDataEventVisibilityDefault },
    { @"entries.0.comment.feedLink.href", @"contains:i12d4avieju0vogcga72aj3908/comments" },
    { @"entries.0.shouldSendEventNotifications", @"0" },
    { @"entries.0.isQuickAdd", @"0" },
    { @"entries.0.transparency", kGDataEventTransparencyOpaque },
    { @"entries.0.eventStatus", kGDataEventStatusConfirmed },
    { @"entries.0.participants.0.email", @"FredFlintstone@gmail.com" },
    { @"entries.0.participants.0.rel", kGDataWhoEventAttendee },
    { @"entries.0.participants.0.attendeeStatus", kGDataWhoAttendeeStatusDeclined },
    { @"entries.0.participants.1.numberOfAdditionalGuests", @"5" },
    { @"entries.0.participants.1.email", @"FredFlintstone@google.com" },
    { @"entries.0.participants.2.email", @"freg@gmail.com" },
    { @"entries.0.times.0.endTime.RFC3339String", @"2006-11-16" },
    { @"entries.0.times.0.reminders.0.minutes", @"10" },
    { @"entries.0.locations.0", @"The-where-field" },
    { @"entries.0.locations.0.rel", nil },
    { @"entries.0.sequenceNumber", @"2" },
    { @"entries.0.iCalUID", @"4A24A0FF-EA3A-4839-AA09-F4283CB6D345" },
    { @"entries.0.canGuestsModify", @"0" },
    { @"entries.0.canGuestsInviteOthers", @"0" },
    { @"entries.0.canGuestsSeeGuests", @"0" },
    { @"entries.0.canAnyoneAddSelf", @"0" },
    { @"entries.1.recurrence", @"hasPrefix:DTSTART;VALUE=DATE:20061120" },
    { @"entries.1.reminders.0.minutes", @"10" },
    { @"entries.1.isDeleted", @"0" },
    { @"entries.3.locations.0", @"Seattle" },
    { @"entries.3.isDeleted", @"1" },
    { @"entries.3.canGuestsModify", @"1" },
    { @"entries.3.canGuestsInviteOthers", @"1" },
    { @"entries.3.canGuestsSeeGuests", @"1" },
    { @"entries.3.canAnyoneAddSelf", @"1" },
    
    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },
    
    { @"", @"" }, // end of feed

    //
    // CalendarEvent Feed with no entries
    //
    { @"GDataFeedCalendarEvent", @"Tests/FeedCalendarEventTest0.xml" },

    // GDataFeedCalendarEvent paths
    { @"title", @"Fred Flintstone" },
    { @"subtitle", @"Fred Flintstone" },
    { @"links.0.rel", kGDataLinkRelFeed },
    { @"links.2.rel", @"self" },
    { @"authors.0.name", @"Fred Flintstone" },
    { @"authors.0.email", @"fred@gmail.com" },
    { @"identifier", @"http://www.google.com/calendar/feeds/test%40gmail.com/private/full" },
    { @"namespaces.gCal", kGDataNamespaceGCal },

    { @"generator.URI", @"http://www.google.com/calendar" },
    { @"generator.name", @"Google Calendar" },
    { @"generator.version", @"1.0" },
    { @"startIndex", @"1" },
    { @"itemsPerPage", @"100000" },
    { @"timeZoneName", @"America/Los_Angeles" },
    { @"timesCleaned", @"7" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // GDataEntryCalendarEvent paths
    { @"entries.@count", @"0" },

    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };
  
  [self runTests:tests];
}

- (void)testContactsFeed {
  
  TestKeyPathValues tests[] =
  { 
    //
    // Contact Feed
    //
    { @"GDataFeedContact/2.0", @"Tests/FeedContactTest1.xml" },
      
    // GDataFeedContact paths 
    { @"title", @"Contacts" },
    { @"categories.0.term", kGDataCategoryContact },
    { @"links.1.rel", kGDataLinkRelPost },
      
    { @"unknownAttributes.@count.stringValue", @"0" },
    { @"unknownChildren.@count.stringValue", @"0" },
      
    // GDataEntryContact paths
    // First entry is real; second entry is deleted
    { @"entries.0.identifier", @"contains:9cfaae9" },
    { @"entries.0.categories.0.term", kGDataCategoryContact },
    { @"entries.0.isDeleted", @"0" },
      
    { @"entries.0.primaryOrganization.orgName", @"Le Company" },
      
    { @"entries.0.organizations.0.orgName", @"Le Company" },
    { @"entries.0.organizations.0.orgTitle", @"Titularstuff" },
    { @"entries.0.organizations.0.label", nil },
    { @"entries.0.organizations.0.rel", kGDataContactOther },
    { @"entries.0.organizations.0.isPrimary", @"1" },
      
    { @"entries.0.organizations.1.orgName", @"Deadhead Associates" },
    { @"entries.0.organizations.1.orgTitle", @"Groupie" },
    { @"entries.0.organizations.1.label", @"DAz" },
    { @"entries.0.organizations.1.rel", nil },
    { @"entries.0.organizations.1.isPrimary", @"0" },
      
    { @"entries.0.primaryIMAddress.address", @"fooaimz" },
      
    { @"entries.0.IMAddresses.0.protocol", kGDataIMProtocolAIM },
    { @"entries.0.IMAddresses.0.address", @"fooaimz" },
    { @"entries.0.IMAddresses.0.label", @"werkz" },
    { @"entries.0.IMAddresses.0.isPrimary", @"1" },
      
    { @"entries.0.IMAddresses.1.protocol", kGDataIMProtocolMSN },
    { @"entries.0.IMAddresses.1.address", @"foomsn" },
    { @"entries.0.IMAddresses.1.label", nil },
    { @"entries.0.IMAddresses.1.rel", kGDataContactHome },
    { @"entries.0.IMAddresses.1.isPrimary", @"0" },
      
    { @"entries.0.IMAddresses.2.protocol", kGDataIMProtocolGoogleTalk },
    { @"entries.0.IMAddresses.2.address", @"foo@gmail.com" },
    { @"entries.0.IMAddresses.2.label", nil },
    { @"entries.0.IMAddresses.2.rel", kGDataContactOther },
    { @"entries.0.IMAddresses.2.isPrimary", @"0" },
      
    { @"entries.0.IMAddresses.3.protocol", kGDataIMProtocolJabber },
    { @"entries.0.IMAddresses.3.address", @"foo@jabber.org" },
    { @"entries.0.IMAddresses.3.label", @"jabz" },
    { @"entries.0.IMAddresses.3.rel", nil },
    { @"entries.0.IMAddresses.3.isPrimary", @"0" },
      
    { @"entries.0.primaryPhoneNumber.stringValue", @"123-4567" },
      
    { @"entries.0.phoneNumbers.0.stringValue", @"123-4567" },
    { @"entries.0.phoneNumbers.0.label", nil },
    { @"entries.0.phoneNumbers.0.rel", kGDataPhoneNumberMobile },
    { @"entries.0.phoneNumbers.0.isPrimary", @"1" },
      
    { @"entries.0.phoneNumbers.1.stringValue", @"333-1414" },
    { @"entries.0.phoneNumbers.1.label", @"shoefone" },
    { @"entries.0.phoneNumbers.1.rel", nil },
    { @"entries.0.phoneNumbers.1.isPrimary", @"0" },
      
    { @"entries.0.primaryPostalAddress.stringValue", @"123 Lane St" },
      
    { @"entries.0.postalAddresses.0.stringValue", @"123 Lane St" },
    { @"entries.0.postalAddresses.0.label", nil },
    { @"entries.0.postalAddresses.0.rel", kGDataContactHome },
    { @"entries.0.postalAddresses.0.isPrimary", @"1" },
      
    { @"entries.0.primaryEmailAddress.address", @"foo@bar.com" },
      
    { @"entries.0.emailAddresses.0.address", @"foo@bar.com" },
    { @"entries.0.emailAddresses.0.label", nil },
    { @"entries.0.emailAddresses.0.rel", kGDataContactHome },
    { @"entries.0.emailAddresses.0.isPrimary", @"1" },
    
    { @"entries.0.emailAddresses.1.address", @"2@bar.com" },
    { @"entries.0.emailAddresses.1.label", @"norzglie" },
    { @"entries.0.emailAddresses.1.rel", nil },
    { @"entries.0.emailAddresses.1.isPrimary", @"0" },
    
    { @"entries.0.groupMembershipInfos.0.href", @"http://www.google.com/m8/feeds/contactGroups/user@gmail.com/full/2" },
    { @"entries.0.groupMembershipInfos.0.isDeleted", @"1" },
      
    { @"entries.0.extendedProperties.0.name", @"com.mycompany.myprop" },
    { @"entries.0.extendedProperties.0.value", @"zoop" },
    { @"entries.0.extendedProperties.0.XMLValues", nil },
    
    { @"entries.0.extendedProperties.1.name", @"com.mycompany.myprop2" },
    { @"entries.0.extendedProperties.1.value", nil },
    { @"entries.0.extendedProperties.1.XMLValues.0.XMLString", @"<myXML><myChild attr=\"nerf\"></myChild></myXML>" },
    { @"entries.0.extendedProperties.1.unknownChildren.@count.stringValue", @"0" },
    
    { @"entries.1.identifier", @"contains:b001135" },
    { @"entries.1.categories.0.term", kGDataCategoryContact },
    { @"entries.1.isDeleted", @"1" },
      
    { @"entries.0.unknownAttributes.@count.stringValue", @"0" },
    { @"entries.0.unknownChildren.@count.stringValue", @"0" },
      
    { @"", @"" }, // end of feed
    
    //
    // Contact Feed with V3 elements
    //
    { @"GDataFeedContact/3.0", @"Tests/FeedContactTest2.xml" },

    // GDataFeedContact paths
    { @"title", @"Fred Flintstone's Contacts" },
    { @"categories.0.term", kGDataCategoryContact },
    { @"ETag", @"W/\"DkYHQHgzfCt7ImA9WxJREEU.\"" },

    { @"unknownAttributes.@count.stringValue", @"0" },
    { @"unknownChildren.@count.stringValue", @"0" },

    // GDataEntryContact paths
    { @"entries.0.identifier", @"contains:754fdf0c0db53ab3" },
    { @"entries.0.categories.0.term", kGDataCategoryContact },
    { @"entries.0.isDeleted", @"0" },
    { @"entries.0.ETag", @"\"Rno_eTVSLyt7ImA9WxJREEUORwc.\"" },
    { @"entries.0.editedDate.RFC3339String", @"2009-05-11T23:20:37Z" },

    { @"entries.0.primaryOrganization.orgName", @"Acme Corp." },

    { @"entries.0.organizations.0.orgName", @"Acme Corp." },
    { @"entries.0.organizations.0.orgTitle", nil },
    { @"entries.0.organizations.0.label", nil },
    { @"entries.0.organizations.0.rel", kGDataContactWork },
    { @"entries.0.organizations.0.isPrimary", @"1" },

    { @"entries.0.primaryIMAddress.address", nil },

    { @"entries.0.IMAddresses.0.protocol", kGDataIMProtocolGoogleTalk },
    { @"entries.0.IMAddresses.0.address", @"fredsim@example.com" },
    { @"entries.0.IMAddresses.0.label", @"main messaging addr" },
    { @"entries.0.IMAddresses.0.rel", nil },
    { @"entries.0.IMAddresses.0.isPrimary", @"0" },

    { @"entries.0.primaryPhoneNumber.stringValue", @"425-555-1234" },

    { @"entries.0.phoneNumbers.0.stringValue", @"425-555-1234" },
    { @"entries.0.phoneNumbers.0.label", @"Grand Central" },
    { @"entries.0.phoneNumbers.0.rel", nil },
    { @"entries.0.phoneNumbers.0.isPrimary", @"1" },

    { @"entries.0.phoneNumbers.1.stringValue", @"425-555-0000" },
    { @"entries.0.phoneNumbers.1.label", nil },
    { @"entries.0.phoneNumbers.1.rel", kGDataPhoneNumberCar },
    { @"entries.0.phoneNumbers.1.isPrimary", @"0" },

    { @"entries.0.primaryStructuredPostalAddress.street", @"301 Cobblestone Way" },

    { @"entries.0.structuredPostalAddresses.0.street", @"301 Cobblestone Way" },
    { @"entries.0.structuredPostalAddresses.0.postCode", @"12345" },
    { @"entries.0.structuredPostalAddresses.0.formattedAddress", @"301 Cobblestone Way\nBedrock, CA 12345\nUnited States" },
    { @"entries.0.structuredPostalAddresses.0.region", @"CA" },
    { @"entries.0.structuredPostalAddresses.0.countryName", @"United States" },
    { @"entries.0.structuredPostalAddresses.0.label", nil },
    { @"entries.0.structuredPostalAddresses.0.rel", kGDataContactHome },
    { @"entries.0.structuredPostalAddresses.0.isPrimary", @"1" },

    { @"entries.0.primaryEmailAddress.address", @"fred@example.com" },

    { @"entries.0.emailAddresses.0.address", @"fred@example.com" },
    { @"entries.0.emailAddresses.0.label", nil },
    { @"entries.0.emailAddresses.0.rel", kGDataContactHome },
    { @"entries.0.emailAddresses.0.isPrimary", @"1" },

    { @"entries.0.groupMembershipInfos.0.href", @"http://www.google.com/m8/feeds/groups/fredflintstone%40example.com/base/6" },
    { @"entries.0.groupMembershipInfos.0.isDeleted", @"0" },

    { @"entries.0.extendedProperties.0.name", @"fredprop" },
    { @"entries.0.extendedProperties.0.value", @"12345" },
    { @"entries.0.extendedProperties.0.XMLValues", nil },

    { @"entries.0.billingInformation", @"account overdue" },

    { @"entries.0.birthday", @"1990-12-01" },
    { @"entries.0.birthdayDate.timeIntervalSince1970", @"660052800" },

    { @"entries.0.calendarLinks.0.rel", nil },
    { @"entries.0.calendarLinks.0.label", @"full calendar" },
    { @"entries.0.calendarLinks.0.href", @"http://www.google.com/calendar/render" },
    { @"entries.0.primaryCalendarLink.label", @"full calendar" },

    { @"entries.0.directoryServer", @"dir server" },

    // { @"entries.0.events.0", @"" },

    { @"entries.0.externalIDs.0.label", @"ext id" },
    { @"entries.0.externalIDs.0.rel", nil },
    { @"entries.0.externalIDs.0.stringValue", @"54321" },

    { @"entries.0.gender", @"male" },

    { @"entries.0.hobbies.0.stringValue", @"gurgling" },

    { @"entries.0.initials", @"F.F." },

    { @"entries.0.jots.0.rel", kGDataContactJotHome },
    { @"entries.0.jots.0.stringValue", @"1248" },

    // { @"entries.0.languages.0", @"" },

    { @"entries.0.maidenName", @"Marshovitzky" },

    { @"entries.0.mileage", @"42 miles" },

    { @"entries.0.name.fullName", @"Fred Flintstone" },
    { @"entries.0.name.givenName", @"Fred" },
    { @"entries.0.name.familyName", @"Flintstone" },

    { @"entries.0.nickname", @"Rocks" },

    { @"entries.0.occupation", @"TV Personality" },

    { @"entries.0.priority", kGDataContactPriorityLow },

    { @"entries.0.relations.0.rel", kGDataContactRelationPartner },
    { @"entries.0.relations.0.label", nil },
    { @"entries.0.relations.0.stringValue", @"Wilma" },

    { @"entries.0.sensitivity", kGDataContactSensitivityNormal },

    { @"entries.0.shortName", @"Freddy" },

    { @"entries.0.subject", @"subject val" },

    { @"entries.0.userDefinedFields.0.key", @"Cat" },
    { @"entries.0.userDefinedFields.0.stringValue", @"Cheezeburger" },

    { @"entries.0.websiteLinks.0.href", @"http://example.com/site.html" },
    { @"entries.0.websiteLinks.0.rel", kGDataContactWebsiteLinkHomePage },
    { @"entries.0.websiteLinks.0.label", nil },
    { @"entries.0.websiteLinks.0.isPrimary", @"0" },

    { @"entries.0.where", @"The Quarry" },

    { @"entries.0.unknownAttributes.@count.stringValue", @"0" },
    { @"entries.0.unknownChildren.@count.stringValue", @"0" },

    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };

  [self runTests:tests];
}

- (void)testSpreadsheetFeeds {
  
  TestKeyPathValues tests[] =
  { 
    //
    // Spreadsheet feed (list of user's spreadsheets)
    //
    { @"GDataFeedSpreadsheet", @"Tests/FeedSpreadsheetTest1.xml" },
    
    // feed paths
    { @"identifier", @"http://spreadsheets.google.com/feeds/spreadsheets/private/full" },
    { @"links.2.href", @"http://spreadsheets.google.com/feeds/spreadsheets/private/full?tfe=" },
    { @"title", @"Available Spreadsheets - test@foo.net" },
    
    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // entry paths
    // There is one entry
    { @"entries.0.identifier", @"http://spreadsheets.google.com/feeds/spreadsheets/private/full/o04181601172097104111.497668944883620000" },
    { @"entries.0.updatedDate.RFC3339String", @"2007-03-22T23:25:53Z" },
    { @"entries.0.categories.2.scheme", kGDataCategorySchemeSpreadsheet },
    { @"entries.0.categories.2.term", kGDataCategorySpreadsheet },
    { @"entries.0.title", @"My Test Spreadsheet" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"1" },

    { @"", @"" }, // end of feed

    // repeat the test, with unknown children turned off
    { @"GDataFeedSpreadsheet-ignoreUnknown", @"Tests/FeedSpreadsheetTest1.xml" },

    // feed paths
    { @"identifier", @"http://spreadsheets.google.com/feeds/spreadsheets/private/full" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // entry paths
    // There is one entry
    { @"entries.0.identifier", @"http://spreadsheets.google.com/feeds/spreadsheets/private/full/o04181601172097104111.497668944883620000" },
    { @"entries.0.updatedDate.RFC3339String", @"2007-03-22T23:25:53Z" },
    
    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },
    
    { @"", @"" }, // end of feed
    
    //
    // Worksheet feed (list of a spreadsheet's worksheets)
    //
    { @"GDataFeedWorksheet", @"Tests/FeedSpreadsheetWorksheetTest1.xml" },
      
    // feed paths
    { @"identifier", @"http://spreadsheets.google.com/feeds/worksheets/o04181601172097104111.497668944883620000/private/full" },
    { @"links.2.href", @"http://spreadsheets.google.com/feeds/worksheets/o04181601172097104111.497668944883620000/private/full?tfe=" },
    { @"title", @"My Test Spreadsheet" },
    { @"authors.0.email", @"test@foo.net" },
        
    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // entry paths
    // There is one entry
    { @"entries.0.identifier", @"http://spreadsheets.google.com/feeds/worksheets/o04181601172097104111.497668944883620000/private/full/od6" },
    { @"entries.0.updatedDate.RFC3339String", @"2007-03-22T23:28:50Z" },
    { @"entries.0.categories.0.scheme", kGDataCategorySchemeSpreadsheet },
    { @"entries.0.categories.0.term", kGDataCategoryWorksheet },
    { @"entries.0.title", @"Sheet1" },
    { @"entries.0.rowCount", @"100" },
    { @"entries.0.columnCount", @"20" },
    
    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },
      
    { @"", @"" }, // end of feed
 
    //
    // Cells feed (all of a worksheet's cells)
    //
    { @"GDataFeedSpreadsheetCell", @"Tests/FeedSpreadsheetCellsTest1.xml" },
    
    // feed paths
    { @"identifier", @"http://spreadsheets.google.com/feeds/cells/o04181601172097104111.497668944883620000/od6/private/full" },
    { @"links.0.href", @"http://spreadsheets.google.com/ccc?key=o04181601172097104111.497668944883620000" },
    { @"categories.0.scheme", kGDataCategorySchemeSpreadsheet },
    { @"categories.0.term", kGDataCategorySpreadsheetCell },
    { @"title", @"Sheet1" },
    { @"authors.0.email", @"test@foo.net" },
    { @"rowCount", @"100" },
    { @"columnCount", @"20" },
  
    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // entry paths
    // The sheet looks like this (2 cols x 4 rows)
    // Fred	Martha
    // =pi()	    =sin(A2)
    // =1.5*pi()	=sin(A3)
    // =2.0*pi()	=sin(A4)
    
    { @"entries.1.identifier", @"http://spreadsheets.google.com/feeds/cells/o04181601172097104111.497668944883620000/od6/private/full/R1C2" },
    { @"entries.1.updatedDate.RFC3339String", @"2007-03-22T23:28:50Z" },
    { @"entries.1.categories.0.scheme", kGDataCategorySchemeSpreadsheet },
    { @"entries.1.categories.0.term", kGDataCategorySpreadsheetCell },
    { @"entries.1.title", @"B1" },
    { @"entries.1.cell.column", @"2" },
    { @"entries.1.cell.row", @"1" },
    { @"entries.1.cell.inputString", @"Martha" },
    { @"entries.1.cell.numericValue", nil },
    { @"entries.1.cell.resultString", @"Martha" },
    
    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // List feed (all of a worksheet as a list) which contains customElements
    //
    
    // feed paths
    { @"GDataFeedSpreadsheetList", @"Tests/FeedSpreadsheetListTest1.xml" },
    { @"identifier", @"http://spreadsheets.google.com/feeds/list/o04181601172097104111.497668944883620000/od6/private/full" },
    { @"links.0.href", @"http://spreadsheets.google.com/ccc?key=o04181601172097104111.497668944883620000" },
    { @"categories.0.scheme", kGDataCategorySchemeSpreadsheet },
    { @"categories.0.term", kGDataCategorySpreadsheetList },
    { @"title", @"Sheet1" },
    { @"authors.0.email", @"test@foo.net" },
    
    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // entry paths
    { @"entries.1.customElementDictionary.fred", @"4.71238898038469" },
    { @"entries.1.customElementDictionary.martha", @"-1" },
    
    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // Table feed
    //

    // feed paths
    { @"GDataFeedSpreadsheetTable", @"Tests/FeedSpreadsheetTableTest1.xml" },
    { @"identifier", @"http://spreadsheets.google.com/feeds/RRHuSwAKiaEGw526z3DVYw/tables" },
    { @"categories.0.scheme", kGDataCategoryScheme },
    { @"categories.0.term", kGDataCategorySpreadsheetTable },
    { @"title", @"Table of Doom" },
    { @"authors.0.email", @"fredflintstone@example.com" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // entry paths
    { @"entries.0.content.sourceURI", @"http://spreadsheets.google.com/feeds/RRHuSwAKiaEGw526z3DVYw/records/1" },
    { @"entries.0.spreadsheetData.startIndex", @"4" },
    { @"entries.0.spreadsheetData.numberOfRows", @"3" },
    { @"entries.0.spreadsheetData.insertionMode", kGDataSpreadsheetModeInsert },
    { @"entries.0.spreadsheetData.columns.0.name", @"Column Beta" },
    { @"entries.0.spreadsheetData.columns.0.indexString", @"B" },
    { @"entries.0.spreadsheetHeader.row", @"3" },
    { @"entries.0.worksheetName", @"Sheet 1" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // Record feed
    //

    // feed paths
    { @"GDataFeedSpreadsheetRecord", @"Tests/FeedSpreadsheetRecordTest1.xml" },
    { @"identifier", @"http://spreadsheets.google.com/feeds/RRHuSwAKiaEGw526z3DVYw/records/1" },
    { @"categories.0.scheme", kGDataCategoryScheme },
    { @"categories.0.term", kGDataCategorySpreadsheetRecord },
    { @"title", @"Records of Doom" },
    { @"authors.0.email", @"fredflintstone@example.com" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // entry paths
    { @"entries.0.content", @"Column Beta: clouds, Column Alpha: mars" },
    { @"entries.0.editLink.href", @"http://spreadsheets.google.com/feeds/RRHuSwAKiaEGw526z3DVYw/records/1/cn6ca" },
    { @"entries.0.fields.0.name", @"Column Beta" },
    { @"entries.0.fields.0.indexString", @"B" },
    { @"entries.0.fields.0.value", @"clouds" },
    { @"entries.0.fields.0.formula", @"3+something" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };

  [self runTests:tests];
}

- (void)testAnalyticsFeeds {

  TestKeyPathValues tests[] =
  {
    //
    // Account feed
    //
    { @"GDataFeedAnalyticsAccount/2.0", @"Tests/FeedAnalyticsAccountTest1.xml" },

    // GDataFeedAnalyticsAccount paths
    { @"authors.0.name", @"Google Analytics" },
    { @"title", @"Profile list for fredflintstone@example.com" },
    { @"segments.0.name", @"All Visits" },
    { @"segments.0.analyticsID", @"gaid::-1" },
    { @"segments.0.definition", @"" },
    { @"segments.1.definition", @"ga:visitorType==New Visitor" },

    // GDataEntryAnalyticsAccount paths
    { @"entries.0.tableID", @"ga:7966084" },
    { @"entries.0.analyticsProperties.0.name", @"ga:accountId" },
    { @"entries.0.analyticsProperties.0.stringValue", @"8925159" },
    { @"entries.0.analyticsProperties.1.name", @"ga:accountName" },
    { @"entries.0.analyticsProperties.1.stringValue", @"example" },
    { @"entries.0.customVariables.0.index", @"3" },
    { @"entries.0.customVariables.0.name", @"User" },
    { @"entries.0.customVariables.0.scope", @"visitor" },
    { @"entries.0.goals.0.isActive", @"1" },
    { @"entries.0.goals.0.name", @"My Main Goal" },
    { @"entries.0.goals.0.number", @"1" },
    { @"entries.0.goals.0.value", @"1.100000023841858" },
    { @"entries.0.goals.0.destination.isCaseSensitive", @"0" },
    { @"entries.0.goals.0.destination.expression", @"/wiki/DocPage" },
    { @"entries.0.goals.0.destination.matchType", @"head" },
    { @"entries.0.goals.0.destination.isStep1Required", @"0" },
    { @"entries.0.goals.0.destination.steps.0.name", @"Wiki step" },
    { @"entries.0.goals.0.destination.steps.0.number", @"1" },
    { @"entries.0.goals.0.destination.steps.0.path", @"/wiki" },
    { @"entries.0.goals.0.engagement", nil },

    { @"", @"" }, // end of feed

    //
    // Data feed
    //
    { @"GDataFeedAnalyticsData/2.0", @"Tests/FeedAnalyticsDataTest1.xml" },

    // GDataFeedAnalyticsData paths
    { @"authors.0.name", @"Google Analytics" },
    { @"title", @"Google Analytics Data for Profile 7966084" },
    { @"startDateString", @"2009-05-18" },
    { @"endDateString", @"2009-05-20" },
    { @"aggregateGroup.metrics.0.confidenceInterval", @"0" },
    { @"aggregateGroup.metrics.0.name", @"ga:pageviews" },
    { @"aggregateGroup.metrics.0.type", kGDataMetricTypeInteger },
    { @"aggregateGroup.metrics.0.stringValue", @"12" },
    { @"aggregateGroup.metrics.0.doubleValue", @"12" },
    { @"dataSources.0.tableID", @"ga:7966084" },
    { @"dataSources.0.tableName", @"www.example.net" },
    { @"dataSources.0.analyticsProperties.0.name", @"ga:profileId" },
    { @"dataSources.0.analyticsProperties.0.stringValue", @"7966084" },
    { @"dataSources.0.analyticsPropertyWithNameAccountName.stringValue", @"example" },
    { @"segments.0.name", @"New Visitors" },
    { @"segments.0.analyticsID", @"gaid::-2" },
    { @"segments.0.definition", @"ga:visitorType==New Visitor" },

    // GDataEntryAnalyticsData paths
    { @"entries.0.title", @"ga:country=United States" },
    { @"entries.0.dimensions.0.name", @"ga:country" },
    { @"entries.0.dimensions.0.stringValue", @"United States" },
    { @"entries.0.dimensionWithNameCountry.stringValue", @"United States" },
    { @"entries.0.metrics.0.confidenceInterval", @"5.1" },
    { @"entries.0.metrics.0.name", @"ga:pageviews" },
    { @"entries.0.metrics.0.type", kGDataMetricTypeInteger },
    { @"entries.0.metrics.0.stringValue", @"37" },
    { @"entries.0.metrics.0.doubleValue", @"37" },
    { @"entries.0.metricWithNamePageviews.stringValue", @"37" },

    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };

  [self runTests:tests];
}


- (void)testCodeSearchFeed {
  
  TestKeyPathValues tests[] =
  { 
    //
    // Feed of a user's albums
    //
    { @"GDataFeedCodeSearch", @"Tests/FeedCodeSearchTest1.xml" },
      
    // GDataFeedCodeSearch paths
    { @"authors.0.name", @"Google Code Search" },
    { @"authors.0.URI", @"http://www.google.com/codesearch" },

    // GDataEntryCodeSearch paths
    { @"entries.0.package.name", @"http://ftp.funet.fi/pub/CPAN/src/perl-5.9.1.tar.gz" },
    { @"entries.0.package.URI", @"http://ftp.funet.fi/pub/CPAN/src/perl-5.9.1.tar.gz" },

    { @"entries.1.package.name", @"http://gentoo.osuosl.org/distfiles/Perl6-Pugs-6.2.12.tar.gz" },
    { @"entries.1.package.URI", @"http://gentoo.osuosl.org/distfiles/Perl6-Pugs-6.2.12.tar.gz" },
    { @"entries.1.file.name", @"Perl6-Pugs-6.2.12/t/subroutines/sub_named_params.t" },
    { @"entries.1.matches.0.lineNumberString", @"131" },
    { @"entries.1.matches.0.type", @"text/html" },
    { @"entries.1.matches.0", @"hasPrefix:<pre>my %fellowship" },
    { @"entries.1.matches.1.lineNumberString", @"132" },
    { @"entries.1.matches.1.type", @"text/html" },
    { @"entries.1.matches.1", @"hasPrefix:<pre>is(%fellowship&lt;hobbit&gt;" },
      
    { @"", @"" }, // end of feed
      
    { nil, nil } // end of test array
  };
  
  [self runTests:tests];
}

- (void)testPhotosFeeds {
  
  // TODO: test geoLocation once we have a good sample of it
  
  // Test a non-ASCII character and some html characters in a TextConstruct.  
  // We'll allocate it dynamically since source code cannot contain non-ASCII.
  NSString *templateStr = @"Test %C Alb%Cm";
  NSString *photoAlbumName = [NSString stringWithFormat:templateStr, 
                              0x262F, 0x00FC]; // yin yang, u with umlaut
  
  // Non-ascii photo description, includes the Wheel of Dharma
  NSString *photoDescriptionText = [NSString stringWithFormat:
                                    @"Caption for the car %C photo", 0x2638];  

  TestKeyPathValues tests[] =
  { 
    //
    // Feed of a user's albums
    //
    { @"GDataFeedPhotoUser", @"Tests/FeedPhotosUserAlbum1.xml" },

    // GDataFeedPhotosAlbum paths
    { @"username", @"TestdomainTestAccount" },
    { @"nickname", @"Greg" },
    { @"thumbnail", @"hasPrefix:http://lh3.google.com/image/TestdomainTestAccount" },
    { @"quotaLimit", @"1073741824" },
    { @"quotaUsed", @"108303" },
    { @"maxPhotosPerAlbum", @"500" },
    { @"categories.0.term", kGDataCategoryPhotosUser },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // GDataEntryPhotoAlbum paths
    { @"entries.0.categories.0.term", kGDataCategoryPhotosAlbum },

    { @"entries.0.mediaGroup.mediaTitle", photoAlbumName },
    { @"entries.0.mediaGroup.mediaDescription", @"Album description" },
    { @"entries.0.mediaGroup.mediaCredits.0", @"Greg" },
    { @"entries.0.mediaGroup.mediaContents.0.medium", @"image" },
    { @"entries.0.mediaGroup.mediaContents.0.type", @"image/jpeg" },
    { @"entries.0.mediaGroup.mediaContents.0.URLString", @"hasPrefix:http://lh5.google.com/image/TestdomainTestAccount" },
    { @"entries.0.mediaGroup.mediaThumbnails.0.height", @"160" },
    { @"entries.0.mediaGroup.mediaThumbnails.0.URLString", @"hasPrefix:http://lh5.google.com/image/TestdomainTestAccount" },

    { @"entries.0.GPhotoID", @"5067143575034336993" },
    { @"entries.0.access", @"public" },
    { @"entries.0.photosUsed", @"2" },
    { @"entries.0.commentCount", @"0" },
    { @"entries.0.bytesUsed", @"108303" },
    { @"entries.0.nickname", @"Greg" },
    { @"entries.0.photosLeft", @"498" },
    { @"entries.0.commentsEnabled", @"1" },
    { @"entries.0.location", @"Album Site" },
    { @"entries.0.timestamp.dateValue.timeIntervalSince1970", @"1179730800" },
    { @"entries.0.username", @"TestdomainTestAccount" },
    { @"entries.0.identifier", @"http://photos.googleapis.com/data/entry/api/user/TestdomainTestAccount/albumid/5067143575034336993" },
    { @"entries.0.title.type", @"text" },
    { @"entries.0.title", photoAlbumName },
    { @"entries.0.photoDescription", @"Album description" },
    { @"entries.0.rightsString", @"public" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // Feed of an album's photos
    //
    { @"GDataFeedPhotoAlbum/2.0", @"Tests/FeedPhotosAlbumPhoto1.xml" },

    // GDataFeedPhotoAlbum - feed paths
    { @"GPhotoID", @"5067143575034336993" },
    { @"access", @"public" },
    { @"photosUsed", @"2" },
    { @"commentCount", @"0" },
    { @"bytesUsed", @"108303" },
    { @"nickname", @"Greg" },
    { @"photosLeft", @"498" },
    { @"commentsEnabled", @"1" },
    { @"location", @"Album Site" },
    { @"timestamp.dateValue.timeIntervalSince1970", @"1179730800" },
    { @"username", @"TestdomainTestAccount" },
    { @"identifier", @"http://photos.googleapis.com/data/feed/api/user/test%40testdomain.net/albumid/5067143575034336993" },
    { @"title.type", @"text" },
    { @"title", photoAlbumName },
    { @"photoDescription", @"Album description" },
    { @"rights", @"public" },
    { @"categories.0.term", kGDataCategoryPhotosAlbum },
    { @"feedLink.href", @"contains:albumid/5067143575034336993" },
    { @"feedLink.rel", kGDataLinkRelFeed },
    { @"postLink.href", @"contains:albumid/5067143575034336993" },
    { @"postLink.rel", kGDataLinkRelPost },
    { @"alternateLink.href", @"contains:/TestAlbM" },
    { @"alternateLink.rel", @"alternate" },
    { @"selfLink.href", @"contains:test%40testdomain.net/albumid/5067143575034336993?start-index=1" },
    { @"selfLink.rel", @"self" },

    { @"firstEntry.GPhotoID", @"5067143579329304306" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // GDataEntryPhoto - entry paths
    { @"entries.0.categories.0.term", kGDataCategoryPhotosPhoto },

    { @"entries.0.checksum", @"23512309abbs298" },
    { @"entries.0.GPhotoID", @"5067143579329304306" },
    { @"entries.0.albumID", @"5067143575034336993" },
    { @"entries.0.width", @"660" },
    { @"entries.0.height", @"433" },
    { @"entries.0.videoStatus", kGDataPhotoStatusReady },
    { @"entries.0.commentsEnabled", @"1" },
    { @"entries.0.size", @"87225" },
    { @"entries.0.commentCount", @"1" },
    { @"entries.0.timestamp.dateValue.timeIntervalSince1970", @"1179786301" },
    { @"entries.0.title", @"Car.jpg" },
    { @"entries.0.photoDescription", photoDescriptionText },
    { @"entries.0.content.sourceURI", @"http://lh3.google.com/image/TestdomainTestAccount/RlIcPQ_TFvI/AAAAAAAAAAs/3fvWtQLN3KI/Car.jpg" },
    { @"entries.0.content.type", @"image/jpeg" },

    { @"entries.0.mediaGroup.mediaTitle", @"Car.jpg" },
    { @"entries.0.mediaGroup.mediaDescription", photoDescriptionText },
    { @"entries.0.mediaGroup.mediaCredits.0", @"Greg" },
    { @"entries.0.mediaGroup.mediaContents.0.medium", @"image" },
    { @"entries.0.mediaGroup.mediaContents.0.type", @"image/jpeg" },
    { @"entries.0.mediaGroup.mediaContents.0.URLString", @"http://lh3.google.com/image/TestdomainTestAccount/RlIcPQ_TFvI/AAAAAAAAAAs/3fvWtQLN3KI/Car.jpg" },
    { @"entries.0.mediaGroup.mediaThumbnails.0.height", @"47" },
    { @"entries.0.mediaGroup.mediaThumbnails.0.width", @"72" },
    { @"entries.0.mediaGroup.mediaThumbnails.0.URLString", @"http://lh3.google.com/image/TestdomainTestAccount/RlIcPQ_TFvI/AAAAAAAAAAs/3fvWtQLN3KI/Car.jpg?imgmax=72" },
    { @"entries.0.mediaGroup.mediaKeywords", @"headlight, red car" },

    { @"entries.0.EXIFTags.tagDictionary.exposure", @"0.0080" },
    { @"entries.0.EXIFTags.tagDictionary.imageUniqueID", @"d8a9e8fd57a384d216f4b2a853d654fc" },

    { @"entries.0.editMediaLink.href", @"contains:5067143579329304306" },
    { @"entries.0.editMediaLink.type", @"image/jpeg" },
    { @"entries.0.alternateLink.href", @"contains:photo#5067143579329304306" },
    { @"entries.0.alternateLink.type", kGDataLinkTypeHTML },
    { @"entries.0.HTMLLink.href", @"contains:photo#5067143579329304306" },
    { @"entries.0.selfLink.href", @"contains:photoid/5067143579329304306" },
    { @"entries.0.selfLink.type", kGDataLinkTypeAtom },
    { @"entries.0.feedLink.href", @"contains:photoid/5067143579329304306" },
    { @"entries.0.feedLink.type", kGDataLinkTypeAtom },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // Feed of a photo's comments
    //
    { @"GDataFeedPhoto/2.0", @"Tests/FeedPhotosPhotoComment1.xml" },

    // GDataFeedPhoto - feed paths
    { @"generator.URI", @"http://photos.google.com/" },
    { @"generator.name", @"Google Photos" },
    { @"generator.version", @"1.00" },
    { @"EXIFTags.tagDictionary.exposure", @"0.0080" },
    { @"categories.0.term", kGDataCategoryPhotosPhoto },
    { @"EXIFTags.tagDictionary.imageUniqueID", @"d8a9e8fd57a384d216f4b2a853d654fc" },
    { @"checksum", @"23512309abbs298" },
    { @"GPhotoID", @"5067143579329304306" },
    { @"albumID", @"5067143575034336993" },
    { @"width", @"660" },
    { @"height", @"433" },
    { @"commentsEnabled", @"1" },
    { @"size", @"87225" },
    { @"commentCount", @"1" },
    { @"timestamp.dateValue.timeIntervalSince1970", @"1179786301" },
    { @"title", @"Car.jpg" },
    { @"photoDescription", photoDescriptionText },
    { @"icon", @"http://lh3.google.com/image/TestdomainTestAccount/RlIcPQ_TFvI/AAAAAAAAAAs/3fvWtQLN3KI/Car.jpg?imgmax=288" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // GDataEntryPhotoComment - entry paths
    { @"entries.0.photoID", @"5067143579329304306" },
    { @"entries.0.GPhotoID", @"5067146044640532244" }, 
    { @"entries.0.categories.0.term", kGDataCategoryPhotosComment },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // Feed of a user's tags
    //

    // GDataFeedPhotoUser - feed paths

    { @"GDataFeedPhotoUser", @"Tests/FeedPhotosUserTag1.xml" },
    { @"username", @"TestdomainTestAccount" },
    { @"nickname", @"Greg" },
    { @"thumbnail", @"hasPrefix:http://lh3.google.com/image/TestdomainTestAccount" },
    { @"quotaLimit", @"1073741824" },
    { @"quotaUsed", @"108303" },
    { @"maxPhotosPerAlbum", @"500" },
    { @"categories.0.term", kGDataCategoryPhotosUser },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // GDataEntryPhotoTag - entry paths
    
    { @"entries.0.title", @"headlight" },
    { @"entries.0.photoDescription", @"headlight" }, 
    { @"entries.0.categories.0.term", kGDataCategoryPhotosTag },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"entries.1.title", @"red car" },
    { @"entries.1.photoDescription", @"red car" }, 
    { @"entries.1.categories.0.term", kGDataCategoryPhotosTag },

    { @"entries.1.unknownAttributes.@count", @"0" },
    { @"entries.1.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // Feed of a user entry
    //
    // This is really a fake feed created by requesting just a single
    // user entry from the picasa server, using
    // GET http://photos.googleapis.com/data/entry/api/user/<username>
    //
    
    // GDataFeedPhotoUser - feed paths (none)
      
    // GDataEntryPhotoUser - entry paths
    { @"GDataFeedPhotoUser", @"Tests/FeedPhotosUserEntry1.xml" },

    { @"entries.0.nickname", @"Greg" },
    { @"entries.0.username", @"TestdomainTestAccount" }, 
    { @"entries.0.thumbnail", @"hasPrefix:http://lh3.google.com/image/TestdomainTestAccount/AAAAUbcFQeo" },
    { @"entries.0.identifier", @"http://photos.googleapis.com/data/entry/api/user/TestdomainTestAccount" },
    { @"entries.0.categories.0.term", kGDataCategoryPhotosUser },
      
    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },
    
    { @"", @"" }, // end of feed

    //
    // Feed of search results
    //

    // feed paths
    { @"GDataFeedPhoto/2.0", @"Tests/FeedPhotosSearch1.xml" },
    { @"title", @"Search Results" },

    // entry paths
    { @"entries.0.title", @"Leangi_Pima_Shetland_Sheepdog_puppies_f2_3d_4.jpg" },
    { @"entries.0.GPhotoID", @"5310251879415742162" },
    { @"entries.0.albumID", @"5310242183362606433" },
    { @"entries.0.albumTitle", @"Shetland Sheepdog - B-litter - Belinda Mali Zmaj & Marvithall Sherwood" },
    { @"entries.0.albumDescription", @"Leangi Pima Shetland Sheepdog puppies" },
    { @"entries.0.location", @"Belgrade, Serbia, Leangi Pima kennel" },
    { @"entries.0.snippet", @"... <b>puppies</b> ..." },
    { @"entries.0.snippetType", kGDataPhotoSnippetTypePhotoTags },
    { @"entries.0.truncated", @"0" },
    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };
  
  [self runTests:tests];
}

- (void)testWebmasterToolsFeeds {
  
  TestKeyPathValues tests[] =
  {
    //
    // Feed of a user's sites
    //
    { @"GDataFeedSite", @"Tests/FeedWebmasterToolsSite1.xml" },

    { @"identifier", @"http://www.google.com/webmasters/tools/feeds/sites" },

    // GDataEntrySite methods

    { @"entries.0.categories.0.term", kGDataCategorySiteInfo },
    { @"entries.0.isIndexed", @"1" },
    { @"entries.0.crawledDate.date.timeIntervalSince1970", @"1206358560" },
    { @"entries.0.isVerified", @"1" },
    { @"entries.0.verificationMethods.@count", @"2" },
    { @"entries.0.verificationMethods.0.type", kGDataSiteVerificationMethodMetatag },
    { @"entries.0.verificationMethods.0.isInUse", @"0" },
    { @"entries.0.verificationMethods.1.type", kGDataSiteVerificationMethodHTMLPage },
    { @"entries.0.verificationMethods.1.isInUse", @"1" },
    { @"entries.0.verificationMethods.1.value", @"google28a5eb30440fabf0.html" },
    { @"entries.0.verificationMethodInUse.value", @"google28a5eb30440fabf0.html" },
    { @"entries.0.verificationEntryLink.href", @"contains:/http%3A%2F%2Fwww.domain.com%2F/verification" },
    { @"entries.0.sitemapsEntryLink.href", @"contains:/http%3A%2F%2Fwww.domain.com%2F/sitemaps" },
    { @"entries.0.geoLocation", @"US" },
    { @"entries.0.preferredDomain", kGDataSiteCrawlPreferredDomainNone },
    { @"entries.0.crawlRate", kGDataSiteCrawlRateNormal },
    { @"entries.0.hasEnhancedImageSearch", @"1" },
    { @"entries.0.entryLinks.@count", @"2" },

    { @"", @"" }, // end of feed

    //
    // Feed of a site's sitemaps
    //
    { @"GDataFeedSitemap", @"Tests/FeedWebmasterToolsSitemap1.xml" },

    { @"identifier", @"contains:http%3A%2F%2Fwww.domain.com%2F/sitemaps" },

    { @"sitemapNews.publicationLabels.@count", @"3" },
    { @"sitemapNews.publicationLabels.2.stringValue", @"a third publabel" },

    { @"sitemapMobile.markupLanguages.@count", @"3" },
    { @"sitemapMobile.markupLanguages.2.stringValue", @"cHTML" },

    // GDataEntrySitemap methods

    { @"entries.0.categories.0.term", kGDataCategorySitemapRegular },
    { @"entries.0.sitemapType", @"WEB" },
    { @"entries.0.sitemapStatus", @"Pending" },
    { @"entries.0.lastDownloadDate", nil },
    { @"entries.0.sitemapURLCount", @"99" },

    { @"entries.1.categories.0.term", kGDataCategorySitemapMobile },
    { @"entries.1.markupLanguage", @"HTML" },
    { @"entries.1.sitemapStatus", @"StatusValue" },
    { @"entries.1.lastDownloadDate.date.timeIntervalSince1970", @"1163878052" },
    { @"entries.1.sitemapURLCount", @"102" },

    { @"entries.2.categories.0.term", kGDataCategorySitemapNews },
    { @"entries.2.publicationLabel", @"pubLabelValue" },
    { @"entries.2.sitemapStatus", @"AnotherStatusValue" },
    { @"entries.2.lastDownloadDate.date.timeIntervalSince1970", @"1163878052" },
    { @"entries.2.sitemapURLCount", @"102" },

    { @"", @"" }, // end of feed

    //
    // Feed of a site's crawl issues
    //
    { @"GDataFeedSiteCrawlIssue", @"Tests/FeedWebmasterToolsCrawlIssues1.xml" },

    { @"identifier", @"contains:http%3A%2F%2Fwww.example.net%2F/crawlissues" },
    { @"title", @"Crawl Issues" },

    // GDataEntrySiteCrawlIssue methods
    { @"entries.0.crawlType", kGDataSiteCrawlTypeWeb },
    { @"entries.0.detectedDate.RFC3339String", @"2008-11-17T01:06:10Z" },
    { @"entries.0.detail", @"4xx Error" },
    { @"entries.0.issueType", @"http-error" },
    { @"entries.0.issueURLString", @"http://example.com/dir/" },
    { @"entries.0.issueLinkedFromURLStrings.0", @"http://example.com" },
    { @"entries.0.issueLinkedFromURLStrings.0.className", @"NSCFString" },

    { @"", @"" }, // end of feed

    //
    // Feed of a site's messages
    //
    { @"GDataFeedSiteMessage", @"Tests/FeedWebmasterToolsMessages1.xml" },
    { @"title", @"Messages" },

    // GDataEntrySiteNessage methods
    { @"entries.0.body", @"Crawl rate change has been requested." },
    { @"entries.0.messageDate.RFC3339String", @"2008-09-18T20:49:57Z" },
    { @"entries.0.language", @"en" },
    { @"entries.0.read", @"1" },
    { @"entries.0.subject", @"hasPrefix:Crawl rate change request" },

    { @"", @"" }, // end of feed

    //
    // Feed of a site's keywords
    //
    { @"GDataFeedSiteKeyword", @"Tests/FeedWebmasterToolsKeywords1.xml" },

    { @"identifier", @"contains:http%3A%2F%2Fwww.example.net%2F/keywords" },

    { @"keywords.@count", @"4" },
    { @"keywordsWithInternalSource.@count", @"1" },
    { @"keywords.0.stringValue", @"example net" },
    { @"keywords.0.source", kGDataSiteKeywordExternal },
    { @"keywordsWithInternalSource.0.stringValue", @"example" },
    { @"keywordsWithInternalSource.0.source", kGDataSiteKeywordInternal },

    // the keywords feed has no entries
    { @"entries", nil },

    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };
  
  [self runTests:tests];
}


- (void)testMessageFeed {
  
  TestKeyPathValues tests[] =
  { 
    //
    // Message Feed
    //
    { @"GDataFeedMessage", @"Tests/FeedMessageTest1.xml" },
      
    // GDataFeedMessage paths
    { @"links.0.href", @"hasPrefix:http://www.google.com/calendar/feeds/default" },
    { @"categories.0.term", kGDataMessage },
      
    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },
      
    // GDataEntryMessage paths
    { @"entries.0.categories.0.term", kGDataMessage },
    { @"entries.0.categories.1.term", kGDataMessageSent },
    { @"entries.0.identifier", @"http://mymail.example.com/feeds/jo/home/full/e1a2af06df8a563edf9d32ec9fd61e03f7f3b67b" },
    { @"entries.0.content", @"Hi, Fritz -- The information you're looking for is on p. 47." },
    { @"entries.0.title", @"Re: Info?" },
    { @"entries.0.participants.0.rel", kGDataWhoMessageFrom },
    { @"entries.0.participants.1.rel", kGDataWhoMessageTo },
      
    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },
      
    { @"", @"" }, // end of feed
      
    { nil, nil } // end of test array
  };
  
  [self runTests:tests];
}


- (void)testDocListFeed {
  
  TestKeyPathValues tests[] =
  { 
    //
    // Docs Feed
    //
    { @"GDataFeedDocList", @"Tests/FeedDocListTest1.xml" },
    
    { @"identifier", @"http://docs.google.com/feeds/documents/private/full" },

    // Docs Entries.  The elements are all standard atom protocol elements,
    // so we are just interested that the proper entry class has been 
    // instantiated 
    { @"entries.0.className", @"GDataEntryStandardDoc" },
    { @"entries.0.categories.0.term", @"http://schemas.google.com/docs/2007#document"},
    { @"entries.0.categories.0.label", @"document"},
    { @"entries.0.lastViewed.date.timeIntervalSince1970", @"1222905296"},
    { @"entries.0.writersCanInvite", @"1"},
    { @"entries.0.lastModifiedBy.name", @"Fred Flintstone"},
    { @"entries.0.quotaBytesUsed", @"12345"},
    { @"entries.0.documentDescription", @"cherry soda"},
    { @"entries.0.isHidden", @"1"},

    { @"entries.1.className", @"GDataEntrySpreadsheetDoc" },
    { @"entries.1.categories.0.term", @"http://schemas.google.com/docs/2007#spreadsheet"},
    { @"entries.1.categories.0.label", @"spreadsheet"},
    
    { @"entries.2.className", @"GDataEntryPresentationDoc" },
    { @"entries.2.categories.0.term", @"http://schemas.google.com/docs/2007#presentation"},
    { @"entries.2.categories.0.label", @"presentation"},
    { @"entries.2.isStarred", @"0"},
  
    { @"entries.3.className", @"GDataEntryPDFDoc" },
    { @"entries.3.categories.0.term", @"http://schemas.google.com/docs/2007#pdf"},
    { @"entries.3.categories.0.label", @"pdf"},
    { @"entries.3.isStarred", @"1"},
    { @"entries.3.isHidden", @"0"},
    { @"entries.3.ACLFeedLink.href", @"contains:pdf54595c7b"},
    { @"entries.3.parentLinks.0.href", @"contains:cc5275b740e3" },

    { @"entries.4.className", @"GDataEntryFolderDoc" },
    { @"entries.4.categories.0.term", @"http://schemas.google.com/docs/2007#folder"},
    { @"entries.4.categories.0.label", @"folder"},

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // DocList User Metadata Entry
    //
    { @"GDataEntryDocListMetadata", @"Tests/EntryDocListMetadataTest1.xml" },

    { @"identifier", @"http://docs.google.com/feeds/metadata/fredflintstone%40example.net" },
    { @"quotaBytesTotal", @"1073741824" },
    { @"quotaBytesUsed", @"124432802" },
    { @"quotaBytesUsedInTrash", @"89564924" },
    { @"exportFormats.0.source", @"presentation" },
    { @"exportFormats.0.target", @"text/plain" },
    { @"importFormats.1.source", @"application/vnd.sun.xml.writer" },
    { @"importFormats.1.target", @"document" },
    { @"features.0.featureName", @"ocr" },
    { @"features.0.featureRate", nil },
    { @"features.1.featureName", @"translation" },
    { @"features.1.featureRate", @"2.0" },
    { @"maxUploadSizes.0.longLongNumberValue", @"512000" },
    { @"maxUploadSizes.0.uploadKind", @"document" },
    { @"maxUploadSizeForKindPDF.longLongNumberValue", @"1048571000" },
    { @"featureForNameUploadAny.featureName", @"upload_any" },
    { @"featureForNameUploadNone", nil },
    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };
  [self runTests:tests];
};

- (void)testFinanceFeed {
  
  TestKeyPathValues tests[] =
  { 
    //
    // Portfolio feed
    //
    { @"GDataFeedFinancePortfolio", @"Tests/FeedFinancePortfolioTest1.xml" },
    
    { @"identifier", @"http://finance.google.com/finance/feeds/default/portfolios" },
    
    // Portfolio entry  
    { @"entries.0.className", @"GDataEntryFinancePortfolio" },
    { @"entries.0.positionURL.absoluteString", @"http://finance.google.com/finance/feeds/default/portfolios/1/positions" },
    { @"entries.0.portfolioData.currencyCode", @"USD" },
    { @"entries.0.portfolioData.gainPercentage", @"10.3" },
    { @"entries.0.portfolioData.return1w", @"12" },
    { @"entries.0.portfolioData.return1y", @"17" },
    { @"entries.0.portfolioData.return3m", @"0" },
    { @"entries.0.portfolioData.return3y", @"23" },
    { @"entries.0.portfolioData.return4w", @"-41" },
    { @"entries.0.portfolioData.return5y", @"10" },
    { @"entries.0.portfolioData.returnOverall", @"12" },
    { @"entries.0.portfolioData.returnYTD", @"6.7" },

    { @"", @"" }, // end of feed

    //
    // Position feed
    //
    { @"GDataFeedFinancePosition", @"Tests/FeedFinancePositionTest1.xml" },
    
    { @"identifier", @"http://finance.google.com/finance/feeds/default/portfolios/1/positions" },
    
    // Position entry  
    { @"entries.0.className", @"GDataEntryFinancePosition" },
    { @"entries.0.transactionURL.absoluteString", @"http://finance.google.com/finance/feeds/default/portfolios/1/positions/NASDAQ:GOOG/transactions" },
    { @"entries.0.symbol.exchange", @"NASDAQ" },
    { @"entries.0.symbol.fullName", @"Google Inc." },
    { @"entries.0.symbol.symbol", @"GOOG" },
    
    { @"entries.0.positionData.shares", @"2" },
    { @"entries.0.positionData.gainPercentage", @"1.3" },
    { @"entries.0.positionData.return1w", @"0.07" },
    { @"entries.0.positionData.return1y", @"-10" },
    { @"entries.0.positionData.return3m", @"172.1" },
    { @"entries.0.positionData.return3y", @"5.4" },
    { @"entries.0.positionData.return4w", @"-4" },
    { @"entries.0.positionData.return5y", @"11" },
    { @"entries.0.positionData.returnOverall", @"12" },
    { @"entries.0.positionData.returnYTD", @"13" },
    { @"", @"" }, // end of feed
    
    //
    // Transaction feed
    //
    { @"GDataFeedFinanceTransaction", @"Tests/FeedFinanceTransactionTest1.xml" },
    
    { @"identifier", @"http://finance.google.com/finance/feeds/default/portfolios/1/positions/NASDAQ%3AAAPL/transactions" },
    
    // Transaction entry  
    { @"entries.0.className", @"GDataEntryFinanceTransaction" },

    { @"entries.0.transactionData.date.date.timeIntervalSince1970", @"1212364800" },
    { @"entries.0.transactionData.shares", @"100" },
    { @"entries.0.transactionData.type", @"Buy" },
    { @"entries.0.transactionData.notes", @"Astrologer&#39;s advice" },
    { @"entries.0.transactionData.commission.moneyWithPrimaryCurrency.amount", @"13.12" },
    { @"entries.0.transactionData.commission.moneyWithPrimaryCurrency.currencyCode", @"USD" },
    { @"entries.0.transactionData.price.moneyWithPrimaryCurrency.amount", @"120" },
    { @"entries.0.transactionData.price.moneyWithPrimaryCurrency.currencyCode", @"USD" },
    { @"entries.0.transactionData.price.moneyWithSecondaryCurrency.amount", @"110" },
    { @"entries.0.transactionData.price.moneyWithSecondaryCurrency.currencyCode", @"CAD" },
    { @"", @"" }, // end of feed
    
    { nil, nil } // end of test array
  };
  [self runTests:tests];
};

- (void)testYouTubeFeeds {
  
  TestKeyPathValues tests[] =
  { 
    //
    // user profile
    // 
    
    // note that the user profile "feed" is really an entry returned by the
    // YouTube server
    { @"GDataEntryYouTubeUserProfile/2.0", @"Tests/FeedYouTubeUserProfile1.xml" },

    // entry elements
    { @"identifier", @"http://gdata.youtube.com/feeds/api/users/TestAccount" },

    // user profile entry elements
    { @"className", @"GDataEntryYouTubeUserProfile" },    

    { @"channelType", @"Standard" },
    { @"statistics.lastWebAccess.RFC3339String", @"2008-01-30T11:38:20-08:00" },
    { @"statistics.videoWatchCount", @"1" },
    { @"statistics.viewCount", @"2" },
    { @"statistics.favoriteCount", @"7" },
    { @"statistics.totalUploadViews", @"37" },
    
    { @"books", @"Booksz" },
    { @"aboutMe", @"Narf narf" },
    { @"age", @"49" },
    { @"thumbnail.URLString", @"http://i.ytimg.com/vi/Bkhnvn1PIHs/default.jpg" },
    { @"company", @"Apple, Google" },
    { @"gender", @"m" },
    { @"hobbies", @"Hobbiez, and more hobbiez" },
    { @"hometown", @"Oakland, CA" },
    { @"location", @"Seattle, WA, US" },
    { @"movies", @"Moviez" },
    { @"music", @"Musicz" },
    { @"occupation", @"worker, player" },
    { @"relationship", @"open" },
    { @"school", @"Wildwood Elementary" },
    { @"username", @"TestAccount" },
    { @"firstName", @"Test" },
    { @"lastName", @"Account" },
    
    { @"feedLinks.@count", @"6" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // video feed
    //
    { @"GDataFeedYouTubeVideo/2.0", @"Tests/FeedYouTubeVideo1.xml" },
    
    // video entry elements
    { @"entries.0.statistics.viewCount", @"177" },
    { @"entries.0.comment.feedLink.href", @"http://gdata.youtube.com/feeds/api/videos/Dl643JFJWig/comments" },
    { @"entries.0.links.0.youTubeToken.stringValue", @"ZeeToken" },
    { @"entries.0.isEmbeddable", @"0" },
    { @"entries.0.rating.numberOfLikes", @"100" },
    { @"entries.0.rating.numberOfDislikes", @"25" },
    { @"entries.0.mediaGroup.duration", @"3266" },
    { @"entries.0.mediaGroup.isPrivate", @"1" },
    { @"entries.0.mediaGroup.mediaPlayers.0.URLString", @"http://www.youtube.com/watch?v=Dl643JFJWig" },
    { @"entries.0.mediaGroup.mediaRatings.0.stringValue", @"1" },
    { @"entries.0.mediaGroup.mediaRatings.0.youTubeCountry", @"KR" },
    { @"entries.0.mediaGroup.mediaThumbnails.0.height", @"97" },
    { @"entries.0.mediaGroup.mediaThumbnails.0.youTubeName", @"abc987" },
    { @"entries.0.publicationState.state", @"rejected" },
    { @"entries.0.publicationState.reasonCode", @"32" },
    { @"entries.0.publicationState.helpURLString", @"http://www.youtube.com/" },
    { @"entries.0.publicationState.errorDescription", @"incorrect format" },
    { @"entries.0.isIncomplete", @"1" },
    { @"entries.0.captionTracksLink.href", @"contains:GppRTi2JGQ8/captions" },
    { @"entries.0.videoResponsesLink.href", @"contains:Dl643JFJWig/responses" },
    { @"entries.0.accessControls.0.action", kGDataYouTubeAccessControlVideoRespond },
    { @"entries.0.accessControls.0.permission", kGDataYouTubeAccessControlPermissionDenied },
    { @"entries.0.accessControls.1.action", kGDataYouTubeAccessControlComment },
    { @"entries.0.accessControls.1.permission", kGDataYouTubeAccessControlPermissionAllowed },
    { @"entries.0.accessControls.1.type", @"group" },
    { @"entries.0.accessControls.1.value", @"friends" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },
      
    { @"", @"" }, // end of feed
    
    //
    // playlist link feed, v2
    //
    
    { @"GDataFeedYouTubePlaylistLink/2.0", @"Tests/FeedYouTubePlaylistLink1.xml" },
    { @"categories.0.term", kGDataCategoryYouTubePlaylistLink },
    { @"totalResults", @"2" },
    { @"startIndex", @"1" },
    { @"itemsPerPage", @"22" },
    
    { @"entries.0.className", @"GDataEntryYouTubePlaylistLink" },    
    { @"entries.0.isPrivate", @"1" },
    { @"entries.0.categories.0.term", kGDataCategoryYouTubePlaylistLink },
    { @"entries.0.categories.1.scheme", kGDataSchemeYouTubeTag },
    { @"entries.0.categories.1.term", @"moreplaylisttags" },
    { @"entries.0.countHint", @"10" },
    
    { @"entries.1.categories.0.scheme", kGDataSchemeYouTubeTag },
    { @"entries.1.categories.0.term", @"PlaylistTags" },
    { @"entries.1.categories.1.term", kGDataCategoryYouTubePlaylistLink },
    { @"entries.1.isPrivate", @"0" },
    
    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },
      
    { @"", @"" }, // end of feed
    
    //
    // playlist feed
    //
      
    { @"GDataFeedYouTubePlaylist", @"Tests/FeedYouTubePlaylist1.xml" },
  
    { @"categories.0.term", kGDataCategoryYouTubePlaylist },
    { @"categories.1.scheme", kGDataSchemeYouTubeTag }, 
    { @"categories.1.term", @"PlaylistTags" },
    { @"totalResults", @"2" },
    { @"startIndex", @"1" },
    { @"itemsPerPage", @"25" },
    
    { @"mediaGroup.mediaContents.0.URLString", @"http://www.youtube.com/ep.swf?id=BBA2B78EF2F08B10" }, 
    { @"mediaGroup.mediaContents.0.youTubeFormatNumber", @"5" }, 
    
    { @"entries.0.className", @"GDataEntryYouTubePlaylist" },    
    { @"entries.0.categories.0.term", kGDataCategoryYouTubePlaylist },
    { @"entries.0.mediaGroup.mediaTitle", @"The Graphing Calculator Story" }, 
    { @"entries.0.mediaGroup.mediaDescription", @"Google TechTalks" }, 
    { @"entries.0.mediaGroup.mediaKeywords.keywords.1", @"avitzur" }, 
    { @"entries.0.mediaGroup.duration", @"3266" }, 
    { @"entries.0.statistics.viewCount", @"185" }, 
    { @"entries.0.position", @"1" }, 
      
    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },
      
    { @"", @"" }, // end of feed
      
    //
    // contacts feed
    //
    { @"GDataFeedYouTubeFriend", @"Tests/FeedYouTubeContacts1.xml" },
      
    { @"categories.0.term", kGDataCategoryYouTubeFriend },

    { @"entries.0.className", @"GDataEntryYouTubeFriend" },    

    { @"entries.0.username", @"fredflintstone" },    
    { @"entries.0.status", @"accepted" },    
      
    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },
      
    { @"", @"" }, // end of feed

    //
    // caption track feed
    //
    { @"GDataFeedYouTubeCaptionTrack", @"Tests/FeedYouTubeCaptionTracks1.xml" },

    { @"categories.0.term", kGDataCategoryYouTubeCaptionTrack },

    { @"entries.0.className", @"GDataEntryYouTubeCaptionTrack" },
    { @"entries.0.title", @"captrack name" },
    { @"entries.0.categories.0.term", kGDataCategoryYouTubeCaptionTrack },

    { @"entries.0.publicationState.state", @"failed" },
    { @"entries.0.publicationState.reasonCode", @"invalidFormat" },
    { @"entries.0.derived", kGDataYouTubeDerivedSpeechRecogniion },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },
    { @"entries.0.unknownChildren", nil },

    { @"", @"" }, // end of feed

    //
    // subscription feed
    //
    { @"GDataFeedYouTubeSubscription/2.0", @"Tests/FeedYouTubeSubscriptions1.xml" },

    // feed elements
    { @"identifier", @"http://gdata.youtube.com/feeds/api/users/testaccount/subscriptions?start-index=1&max-results=25" },
    { @"logo", @"http://www.youtube.com/img/pic_youtubelogo_123x63.gif" },

    // subscription entry elements
    { @"entries.0.className", @"GDataEntryYouTubeSubscription" },    

    { @"entries.0.subscriptionType", @"channel" },
    { @"entries.0.username", @"googletechtalks" },
    { @"entries.0.content.sourceURI", @"http://gdata.youtube.com/feeds/api/users/googletechtalks/uploads" },
    { @"entries.0.countHint", @"2" },
    { @"entries.0.relatedLink.href", @"http://gdata.youtube.com/feeds/api/users/TestAccount" },
    
    { @"entries.1.youTubeQueryString", @"cute dogs" },
    { @"entries.1.playlistID", @"1sbd45" },
    { @"entries.1.playlistTitle", @"Dog Playlist" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // favorites feed
    //
    { @"GDataFeedYouTubeFavorite/2.0", @"Tests/FeedYouTubeFavorites1.xml" },

    // feed elements
    { @"ETag", @"W/\"CUACQX47eCp7ImA9WxdSEkg.\"" },
    { @"identifier", @"tag:youtube,2008:user:fredflintstone:favorites" },
    { @"logo", @"http://www.youtube.com/img/pic_youtubelogo_123x63.gif" },

    // favorites entry elements
    { @"entries.0.className", @"GDataEntryYouTubeFavorite" },
    { @"entries.0.ETag", @"W/\"CUACQX47eCp7ImA9WxdSEkg.\"" },
    { @"entries.0.identifier", @"tag:youtube,2008:favorite:vjVQa1PpcFP8fBdcAc_YTSRHgC1CE_ageg03_RO2EVk" },
    { @"entries.0.content.sourceURI", @"http://www.youtube.com/v/I-t-7lTw6mA&f=gdata_user_favorites" },

    { @"entries.0.location", @"mountain view, ca" },
    { @"entries.0.recordedDate", @"2008-03-04" },

    { @"entries.0.mediaGroup.mediaTitle", @"Authors@Google: Michael Pollan" },
    { @"entries.0.mediaGroup.mediaDescription", @"Michael Pollan visits Google" },
    { @"entries.0.mediaGroup.videoID", @"I-t-7lTw6mA" },
    { @"entries.0.mediaGroup.aspectRatio", @"widescreen" },
    { @"entries.0.mediaGroup.uploadedDate.date.timeIntervalSince1970", @"1204847352" },
    { @"entries.0.mediaGroup.mediaCredits.0.role", @"uploader" },
    { @"entries.0.mediaGroup.mediaCredits.0.scheme", @"urn:youtube" },
    { @"entries.0.mediaGroup.mediaCredits.0.youTubeCreditType", @"partner" },
    { @"entries.0.mediaGroup.mediaContents.0.duration", @"3554" },
    { @"entries.0.mediaGroup.mediaContents.0.expression", @"full" },
    { @"entries.0.mediaGroup.mediaContents.0.medium", @"video" },
    { @"entries.0.mediaGroup.mediaContents.0.type", @"application/x-shockwave-flash" },
    { @"entries.0.mediaGroup.mediaContents.0.URLString", @"http://www.youtube.com/v/I-t-7lTw6mA&f=gdata_user_favorites" },
    { @"entries.0.mediaGroup.mediaContents.0.youTubeFormatNumber", @"5" },
    { @"entries.0.mediaGroup.highQualityThumbnail.URLString", @"http://i.ytimg.com/vi/I-t-7lTw6mA/0.jpg" },
    { @"entries.0.mediaGroup.mediaContentWithFormatNumber1.type", @"video/3gpp" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // user events feed
    //
    { @"GDataFeedYouTubeUserEvent/2.0", @"Tests/FeedYouTubeUserEventTest1.xml" },

    // feed elements
    { @"ETag", @"W/\"AkUBRH47eCp7ImA9WxVWFkU.\"" },
    { @"identifier", @"tag:youtube.com,2008:events" },

    // entry elements
    { @"entries.0.className", @"GDataEntryYouTubeUserEvent" },
    { @"entries.0.kindCategory.term", kGDataCategoryYouTubeUserEvent },
    { @"entries.0.ETag", @"W/\"AkUBRH47eCp7ImA9WxVWFkU.\"" },
    { @"entries.0.identifier", @"tag:youtube.com,2008:event:Z3JlZ3JvYmJpbnM3MTIzNTY4ODI1NUJlbGFuYXI%3D" },
    { @"entries.0.username", @"wilmaflintstone" },
    { @"entries.0.userEventType", kGDataYouTubeUserEventFriendAdded },
    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"entries.1.userEventType", kGDataYouTubeUserEventVideoFavorited },
    { @"entries.1.videoLink.href", @"http://gdata.youtube.com/feeds/api/videos/cxFfUsDgnaU" },
    { @"entries.1.videoID", @"cxFfUsDgnaU" },
    { @"entries.1.unknownAttributes.@count", @"0" },
    { @"entries.1.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed
    
    { nil, nil } // end of test array
  };
  [self runTests:tests];
};

- (void)testHealthFeed {

  TestKeyPathValues tests[] =
  {
    //
    // Health Profile Feed
    //

    { @"GDataFeedHealthProfile", @"Tests/FeedHealthProfile1.xml" },

    // feed elements
    { @"ETag", @"W/\"AkMFSXs6fip7ImA9WxVXEk0.\"" },
    { @"identifier", @"https://www.google.com/h9/feeds/profile/ui/wwYexvc_W.Q" },
    { @"categories.0.term", kGDataCategoryH9Profile },
    { @"categories.0.scheme", kGDataCategoryScheme },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // GDataEntryHealthProfile paths
    //
    // unique elements are continuityOfCareRecord and profileMetaData

    { @"entries.0.contributors.0.name", @"Fred Flintstone" },
    { @"entries.0.categories.0.term", @"MEDICATION" },
    { @"entries.0.categories.1.scheme", kGDataHealthSchemeItem },
    { @"entries.0.categories.1.term", @"A-200 Lice Treatment" },
    { @"entries.0.categories.2.term", kGDataCategoryH9Profile },
    { @"entries.0.identifier", @"https://www.google.com/h9/feeds/profile/ui/wwYexvc_W.Q/wXQ4TZbjHAE" },
    { @"entries.0.continuityOfCareRecord.childXMLElements.0.XMLString", @"<CCRDocumentObjectID>wXQ4TZbjHAE</CCRDocumentObjectID>" },
    { @"entries.0.profileMetaData.childXMLElements.0.XMLString", @"<UserComment src=\"IRI\" type=\"text\"></UserComment>" },
    { @"entries.0.CCRCategory.term", @"MEDICATION" },
    { @"entries.0.healthItemCategory.term", @"A-200 Lice Treatment" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };

  [self runTests:tests];
}

- (void)testMapsFeed {

  TestKeyPathValues tests[] =
  {
    //
    // Map Feature Feed
    //

    { @"GDataFeedMapFeature/2.0", @"Tests/FeedMapFeaturesTest1.xml" },

    // feature feed elements
    { @"title", @"Kirkland Office" },
    { @"identifier", @"http://maps.google.com/maps/feeds/features/2063624404192119555/0046a899354a8585d4d" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // feature entry elements
    { @"entries.0.editedDate.RFC3339String", @"2009-05-23T02:10:51Z" },
    { @"entries.0.categories.0.term", kGDataCategoryMapFeature },

    { @"entries.0.KMLValues.@count", @"1" },
    { @"entries.0.KMLValues.0.name", @"Placemark" },
    { @"entries.0.viewLink.href", @"contains:view/00047730f33bb0097204c" },

    { @"entries.0.postalAddress.city", @"Anytown" },

    { @"entries.0.customProperties.@count", @"2" },
    { @"entries.0.customProperties.0.name", @"distance" },
    { @"entries.0.customProperties.0.unit", @"meters" },
    { @"entries.0.customProperties.0.value", @"36" },
    { @"entries.0.customPropertyWithHeightName.value", @"10.2" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };

  [self runTests:tests];
}

- (void)testBlogFeeds {

  TestKeyPathValues tests[] =
  {
    //
    // Blog feed
    //

    { @"GDataFeedBlog/2.0", @"Tests/FeedBlogTest1.xml" },

    // feed elements
    { @"title", @"TestAccount's Blogs" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // entry elements - no extensions, so just test convenience accessors
    { @"entries.0.templateLink.href", @"http://www.blogger.com/feeds/145278007186576769/template" },
    { @"entries.0.settingsLink.href", @"http://www.blogger.com/feeds/145278007186576769/settings" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // Blog post feed
    //

    { @"GDataFeedBlogPost/2.0", @"Tests/FeedBlogPostTest1.xml" },

    // feed elements
    { @"title", @"Test Posts" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // entry elements
    { @"entries.0.atomPubControl.isDraft", @"1" },
    { @"entries.0.identifier", @"tag:blogger.com,1999:blog-1452780071865767697.post-6317479243618470383" },
    { @"entries.0.total", @"3" },
    { @"entries.0.thumbnail.URLString", @"contains:Hippo.jpg" },
    { @"entries.0.repliesHTMLLink.href", @"https://www.blogger.com/comment.g?blogID=1452780071865767697&postID=6317479243618470383" },
    { @"entries.0.repliesAtomLink.href", @"http://testaccount.blogspot.com/feeds/6317479243618470383/comments/default" },
    { @"entries.0.enclosureLink.href", @"http://example.net/file.txt" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    //
    // Blog comments feed
    //

    { @"GDataFeedBlogComment/2.0", @"Tests/FeedBlogCommentsTest1.xml" },

    // feed elements
    { @"title", @"Comments on Post" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // entry elements
    { @"entries.0.inReplyTo.href", @"http://testaccount.blogspot.com/2009/06/post-created-2009-06-02-161600-0700.html" },
    { @"entries.0.inReplyTo.ref", @"tag:blogger.com,1999:blog-1452780071865767697.post-1015986829942539734" },
    { @"entries.0.inReplyTo.source", @"http://testaccount.blogspot.com/feeds/posts/default/1015986829942539734" },
    { @"entries.0.inReplyTo.type", kGDataLinkTypeHTML },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };

  [self runTests:tests];
}

- (void)testACLFeed {
  
  TestKeyPathValues tests[] =
  {
    //
    // ACL Feed
    //
    { @"GDataFeedACL", @"Tests/FeedACLTest1.xml" },

    // GDataFeedACL paths
    { @"links.0.href", @"http://www.google.com/calendar/feeds/test%40gmail.com/private/full" },
    { @"links.0.rel", kGDataLinkRelControlledObject },
    { @"categories.0.term", kGDataCategoryACL },
    { @"categories.0.scheme", kGDataCategoryScheme },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    // GDataEntryACL paths (scope and role are the main elements)
    { @"entries.0.categories.0.term", kGDataCategoryACL },
    { @"entries.0.identifier", @"http://www.google.com/calendar/feeds/test%40gmail.com/acl/full/user%3Atest%40gmail.com" },
    { @"entries.0.content", @"" },
    { @"entries.0.links.1.rel", @"edit" },
    { @"entries.0.scope.type", @"user" },
    { @"entries.0.scope.value", @"test@example.com" },
    { @"entries.0.role.value", @"http://schemas.google.com/gCal/2005#owner" },

    { @"entries.0.unknownAttributes.@count", @"0" },
    { @"entries.0.unknownChildren.@count", @"0" },

    { @"entries.1.scope.type", @"user" },
    { @"entries.1.scope.name", @"wilma" },
    { @"entries.1.keyedRole.key", @"A123B" },
    { @"entries.1.keyedRole.role.value", @"reader" },

    { @"entries.1.unknownAttributes.@count", @"0" },
    { @"entries.1.unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };
  
  [self runTests:tests];
}

- (void)testServiceDocuments {

  TestKeyPathValues tests[] =
  {
    //
    // service document
    //
    { @"GDataAtomServiceDocument", @"Tests/FeedServiceDocTest2.xml" },

    // GDataAtomServiceDocument paths
    { @"namespaces.app", kGDataNamespaceAtomPub },
    { @"workspaces.@count", @"2" },

    // workspace paths
    { @"workspaces.0.title", @"fredflintstone workspace" },
    { @"workspaces.0.collections.@count", @"1" },
    { @"workspaces.0.collections.0.title", @"filetypes" },
    { @"workspaces.0.collections.0.serviceAcceptStrings.@count", @"5" },
    { @"workspaces.0.collections.0.serviceAcceptStrings.0", @"image/png" },
    { @"workspaces.0.collections.0.serviceAcceptStrings.1", @"image/jpeg" },

    { @"workspaces.1.title", @"Second Workspace" },
    { @"workspaces.1.collections.@count", @"1" },
    { @"workspaces.1.collections.0.href", @"http://example.org/hrefcollection" },
    { @"workspaces.1.collections.0.title", @"Remaindered Links" },
    { @"workspaces.1.collections.0.serviceAcceptStrings.@count", @"1" },
    { @"workspaces.1.collections.0.serviceAcceptStrings.0", @"entry" },
    { @"workspaces.1.collections.0.categoryGroup.isFixed", @"1" },
    { @"workspaces.1.collections.0.categoryGroup.scheme", @"bogusscheme" },
    { @"workspaces.1.collections.0.categoryGroup.href", @"bogushref" },
    { @"workspaces.1.collections.0.categoryGroup.categories.@count", @"2" },
    { @"workspaces.1.collections.0.categoryGroup.categories.1.term", @"serious" },

    { @"unknownAttributes.@count", @"0" },
    { @"unknownChildren.@count", @"0" },

    { @"", @"" }, // end of feed

    { nil, nil } // end of test array
  };

  [self runTests:tests];
}

- (void)testDetachedEntries {

  // get a feed, retain the entries, release the feed, then verify that the
  // entries are usable with the feed itself gone

  NSData *data = [NSData dataWithContentsOfFile:@"Tests/FeedCalendarEventTest1.xml"];
  STAssertNotNil(data, @"Cannot read feed for detach test");

  // create the feed object
  GDataFeedBase *feed = [[GDataFeedCalendarEvent alloc] initWithData:data
                                                      serviceVersion:@"2.1"
                                                shouldIgnoreUnknowns:NO];
  STAssertNotNil(feed, @"Cannot make detaching feed");

  NSArray *entries = [[[feed entries] retain] autorelease];
  STAssertTrue([entries count] > 0, @"Feed lacks entries to detach");

  [feed release];

  GDataEntryBase *firstEntry = [entries objectAtIndex:0];
  NSString *title = [[firstEntry title] stringValue];
  STAssertEqualObjects(title, @"3 days", @"testing an element in a detached entry");

  NSString *titleType = [[firstEntry title] type];
  STAssertEqualObjects(titleType, @"text", @"testing an attribute in a detached entry");
}

@end

///////////////////////////////////////////////////////////////////////////
//
// categories to test helper methods that require arguments
//

//
// YouTube
//

@interface GDataYouTubeMediaGroup (TestHelperMethods)
@end

@implementation GDataYouTubeMediaGroup (TestHelperMethods)
- (GDataMediaContent *)mediaContentWithFormatNumber1 {
  return [self mediaContentWithFormatNumber:kGDataYouTubeMediaContentFormatRTSPStream];
}
@end

//
// analytics
//

@interface GDataAnalyticsDataSource (TestHelperMethods)
- (GDataAnalyticsProperty *)analyticsPropertyWithNameAccountName;
@end

@implementation GDataAnalyticsDataSource (TestHelperMethods)
- (GDataAnalyticsProperty *)analyticsPropertyWithNameAccountName {
  return [self analyticsPropertyWithName:@"ga:accountName"];
}
@end


@interface GDataEntryAnalyticsData (TestHelperMethods)
- (GDataAnalyticsDimension *)dimensionWithNameCountry;
- (GDataAnalyticsMetric *)metricWithNamePageviews;
@end

@implementation GDataEntryAnalyticsData (TestHelperMethods)
- (GDataAnalyticsDimension *)dimensionWithNameCountry {
  return [self dimensionWithName:@"ga:country"];
}

- (GDataAnalyticsMetric *)metricWithNamePageviews {
  return [self metricWithName:@"ga:pageviews"];
}
@end

//
// maps
//

@interface GDataEntryMapFeature (TestHelperMethods)
- (GDataCustomProperty *)customPropertyWithHeightName;
@end

@implementation GDataEntryMapFeature (TestHelperMethods)
- (GDataCustomProperty *)customPropertyWithHeightName {
  return [self customPropertyWithName:@"height"];
}
@end

//
// DocList User Metadata
//

@interface GDataEntryDocListMetadata (TestHelperMethods)
- (GDataDocMaxUploadSize *)maxUploadSizeForKindPDF;
- (GDataDocFeature *)featureForNameUploadAny;
- (GDataDocFeature *)featureForNameUploadNone;
@end

@implementation GDataEntryDocListMetadata (TestHelperMethods)
- (GDataDocMaxUploadSize *)maxUploadSizeForKindPDF {
  return [self maxUploadSizeForKind:@"pdf"];
}

- (GDataDocFeature *)featureForNameUploadAny {
  return [self featureForName:@"upload_any"];
}

- (GDataDocFeature *)featureForNameUploadNone {
  return [self featureForName:@"upload_none"];
}

@end

//
// webmaster tools
//

@interface GDataFeedSiteKeyword (TestHelperMethods)
- (NSArray *)keywordsWithInternalSource;
@end

@implementation GDataFeedSiteKeyword (TestHelperMethods)
- (NSArray *)keywordsWithInternalSource {
  return [self keywordsWithSource:kGDataSiteKeywordInternal];
}
@end
